package com.lin.filegraph.utils.parser.astvisitor;

import com.lin.filegraph.utils.parser.node.JFileNode;
import org.eclipse.jdt.core.dom.*;

import java.util.ArrayList;
import java.util.List;

/**
 * @description:
 * @author: linhuaixu
 * @time: 2023/5/18 11:04
 */
public class CognCompVisitor extends ASTVisitor {
    private List<CognCompOperator> listOperators = new ArrayList<>();
    private JFileNode fileNode;// 当前分析的方法
    private String currentMethodSignature = null;

    public CognCompVisitor(JFileNode fileNode) {
        this.fileNode = fileNode;
    }

    public void traverse() {
        ASTNode astFileNode = fileNode.getAstNode();
        astFileNode.accept(this);
    }

    public List<CognCompOperator> getListOperators() {
        return listOperators;
    }

    private void addOperator(CognCompOperator operator) {
        listOperators.add(operator);
    }

    @Override
    public boolean visit(MethodInvocation node) {
        if(currentMethodSignature == null || currentMethodSignature.equals("")) {
            return true;
        }
        if(node.resolveMethodBinding() != null) {
            String invMethodSignature = node.resolveMethodBinding().toString();
            if(currentMethodSignature.equals(invMethodSignature)) {
//				System.out.println(fileNode.getFilePath());
//				System.out.println("currentMethodSignature:" + currentMethodSignature);
//				System.out.println("invMethodSignature:" + invMethodSignature);

                CognCompOperator operator = new CognCompOperator(CognCompOperatorType.NESTED_METHOD, node.getStartPosition(),
                        0);
                addOperator(operator);
            }
        }
        return true;
    }

    @Override
    public boolean visit(MethodDeclaration node) {
        if(node.resolveBinding() != null) {
            String currentMethod = node.resolveBinding().toString();
            if(currentMethod != null && !currentMethod.equals("")) {
                this.currentMethodSignature = currentMethod;
            }
        }
        return true;
    }

    @Override
    public void endVisit(MethodDeclaration node) {
        this.currentMethodSignature = null;
    }
    @Override
    public boolean visit(IfStatement node) {
        // ELSE_IF，已经处理过
        if (getOperatorByLocation(node.getThenStatement().getStartPosition()) != null) {
            return true;
        }
        int extraCognComp = getConditionalComp(node.getExpression().toString());
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.IF,
                node.getThenStatement().getStartPosition(), extraCognComp);
        addOperator(operator);
        setDepth(node.getThenStatement());
        if (node.getElseStatement() != null) {
            if (node.getElseStatement() instanceof IfStatement) {
                // ELSE_IF
                IfStatement elseIfStatement = (IfStatement) node.getElseStatement();
                int elseIfExtraCognComp = getConditionalComp(elseIfStatement.getExpression().toString());
                CognCompOperator elseIfOperator = new CognCompOperator(CognCompOperatorType.ELSEIF,
                        elseIfStatement.getThenStatement().getStartPosition(), elseIfExtraCognComp);
                addOperator(elseIfOperator);
                setDepth(elseIfStatement.getThenStatement());
            } else {
                // ELSE
                CognCompOperator elseOperator = new CognCompOperator(CognCompOperatorType.ELSE,
                        node.getElseStatement().getStartPosition(), 0);
                addOperator(elseOperator);
                setDepth(node.getElseStatement());
            }

        }
        return true;
    }

    @Override
    public boolean visit(ConditionalExpression node) {
        int extraCognComp = getConditionalComp(node.getExpression().toString());
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.TERNARY_OPERATOR, node.getStartPosition(),
                extraCognComp);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(SwitchCase node) {
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.SWITCH, node.getStartPosition(), 0);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(ForStatement node) {
        int extraCognComp = 0;
        if (node.getExpression() != null) {
            extraCognComp = getConditionalComp(node.getExpression().toString());
        }
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.FOR, node.getStartPosition(),
                extraCognComp);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(EnhancedForStatement node) {
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.FOREACH, node.getStartPosition(), 0);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(WhileStatement node) {
        int extraCognComp = getConditionalComp(node.getExpression().toString());
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.WHILE, node.getStartPosition(),
                extraCognComp);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(DoStatement node) {
        int extraCognComp = getConditionalComp(node.getExpression().toString());
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.DO_WHILE, node.getStartPosition(),
                extraCognComp);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(CatchClause node) {
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.CATCH, node.getStartPosition(), 0);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    @Override
    public boolean visit(ContinueStatement node) {
        if (node.getLabel() != null) {
            CognCompOperator operator = new CognCompOperator(CognCompOperatorType.CONTINUE, node.getStartPosition(), 0);
            addOperator(operator);
        }
        return true;
    }

    @Override
    public boolean visit(BreakStatement node) {
        if (node.getLabel() != null) {
            CognCompOperator operator = new CognCompOperator(CognCompOperatorType.BREAK, node.getStartPosition(), 0);
            addOperator(operator);
        }

        return true;
    }

    @Override
    public boolean visit(AnonymousClassDeclaration node) {
        CognCompOperator operator = new CognCompOperator(CognCompOperatorType.LAMBDAS, node.getStartPosition(), 0);
        addOperator(operator);
        setDepth(node);
        return true;
    }

    private void setDepth(ASTNode node) {
        List<CognCompOperator> nodesChain = new ArrayList<>();
        ASTNode currentNode = node;
        do {
            CognCompOperator operator = getOperatorByLocation(currentNode.getStartPosition());
            if (operator != null) {
                if (operator.getType().isNested() || operator.getLocation() == node.getStartPosition())
                    nodesChain.add(operator);
                if (operator.getNestDepth() > 0) {
                    break;
                }
            }

            currentNode = currentNode.getParent();
        } while (currentNode != null);
        int depth = 0;

        for (int i = nodesChain.size() - 1; i >= 0; i--) {
            if (i == nodesChain.size() - 1) {
                depth = nodesChain.get(i).getNestDepth();
            } else {
                nodesChain.get(i).setNestDepth(++depth);
            }
        }
    }

    private int getConditionalComp(String expression) {
        int conditionalComp = 0;
        CognCompOperatorType type = null;
        char[] expressionChars = expression.toCharArray();
        for (int i = 0; i < expressionChars.length - 1; i++) {
            if (expressionChars[i] == '&' && expressionChars[i + 1] == '&') {
                if (type == null) {
                    type = CognCompOperatorType.CONDITIONAL_AND;
                    conditionalComp++;
                } else {
                    if (!type.equals(CognCompOperatorType.CONDITIONAL_AND)) {
                        type = CognCompOperatorType.CONDITIONAL_AND;
                        conditionalComp++;
                    }
                }
                i++;
            }
            if (expressionChars[i] == '|' && expressionChars[i + 1] == '|') {
                if (type == null) {
                    type = CognCompOperatorType.CONDITIONAL_OR;
                    conditionalComp++;
                } else {
                    if (!type.equals(CognCompOperatorType.CONDITIONAL_OR)) {
                        type = CognCompOperatorType.CONDITIONAL_OR;
                        conditionalComp++;
                    }
                }
                i++;
            }
        }
        return conditionalComp;
    }

    private CognCompOperator getOperatorByLocation(int location) {
        for (CognCompOperator operator : getListOperators()) {
            if (operator.getLocation() == location) {
                return operator;
            }
        }
        return null;
    }

    public int getCognComp() {
        int cognComp = 0;
        for (CognCompOperator operator : getListOperators()) {
            int currentCognComp = 0;
            CognCompOperatorType type = operator.getType();
            if (type.isFundamental()) {
                currentCognComp += 1 + operator.getExtraCognComp();
            }
            if (type.isFundamentalAndNested()) {
                currentCognComp += operator.getNestDepth();
            }
            cognComp += currentCognComp;
            // System.out.println(operator + " Line:" +
            // fileNode.getCompilationUnit().getLineNumber(operator.getLocation())
            // + " CognComp:" + currentCognComp);
        }
        return cognComp;

    }
}

class CognCompOperator {
    private CognCompOperatorType type;
    private int location;
    private int extraCognComp;
    private int nestDepth = 0;

    public CognCompOperator(CognCompOperatorType type, int location, int extraCognComp) {
        super();
        this.type = type;
        this.location = location;
        this.extraCognComp = extraCognComp;
    }

    public CognCompOperatorType getType() {
        return type;
    }

    public int getLocation() {
        return location;
    }

    public int getNestDepth() {
        return nestDepth;
    }

    public void setNestDepth(int nestDepth) {
        this.nestDepth = nestDepth;
    }

    public int getExtraCognComp() {
        return extraCognComp;
    }

    public void setExtraCognComp(int extraCognComp) {
        this.extraCognComp = extraCognComp;
    }

    @Override
    public String toString() {
        return "type:" + type + " extraCognComp:" + extraCognComp + " nestDepth:" + nestDepth;

    }

}

enum CognCompOperatorType {
    IF, ELSEIF, ELSE,

    CONDITIONAL_AND, CONDITIONAL_OR,

    TERNARY_OPERATOR,

    SWITCH,

    FOR, FOREACH, WHILE, DO_WHILE,

    CATCH,

    GOTO, BREAK, CONTINUE,

    NESTED_METHOD,

    LAMBDAS;
    public boolean isFundamental() {
        switch (this) {
            case IF:
            case ELSEIF:
            case ELSE:
            case TERNARY_OPERATOR:
            case SWITCH:
            case FOR:
            case FOREACH:
            case WHILE:
            case DO_WHILE:
            case CATCH:
            case GOTO:
            case BREAK:
            case CONTINUE:
            case NESTED_METHOD:
                return true;
            default:
                return false;
        }
    }

    public boolean isNested() {
        switch (this) {
            case IF:
            case ELSEIF:
            case ELSE:
            case TERNARY_OPERATOR:
            case SWITCH:
            case FOR:
            case FOREACH:
            case WHILE:
            case DO_WHILE:
            case CATCH:
            case NESTED_METHOD:
            case LAMBDAS:
                return true;
            default:
                return false;
        }
    }

    public boolean isFundamentalAndNested() {
        switch (this) {
            case IF:
            case TERNARY_OPERATOR:
            case SWITCH:
            case FOR:
            case FOREACH:
            case WHILE:
            case DO_WHILE:
            case CATCH:
                return true;
            default:
                return false;
        }
    }

}

